home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / cmds / man / man.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-03-06  |  28.4 KB  |  1,208 lines

  1. /* 
  2.  * man.c --
  3.  *
  4.  *    This file contains the "man" program for Sprite.  See the man
  5.  *    page for details on how it works.
  6.  *
  7.  * Copyright 1988 Regents of the University of California
  8.  * Permission to use, copy, modify, and distribute this
  9.  * software and its documentation for any purpose and without
  10.  * fee is hereby granted, provided that the above copyright
  11.  * notice appear in all copies.  The University of California
  12.  * makes no representations about the suitability of this
  13.  * software for any purpose.  It is provided "as is" without
  14.  * express or implied warranty.
  15.  */
  16.  
  17. #ifndef lint
  18. static char rcsid[] = "$Header: /sprite/src/cmds/man/RCS/man.c,v 1.10 91/08/15 23:13:46 ouster Exp Locker: shirriff $ SPRITE (Berkeley)";
  19. #endif not lint
  20.  
  21. #include <ctype.h>
  22. #include <errno.h>
  23. #include <option.h>
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <string.h>
  27. #include <sys/types.h>
  28. #include <sys/file.h>
  29. #include <sys/stat.h>
  30. #include <sys/dir.h>
  31.  
  32. /*
  33.  * Name of default configuration file:
  34.  */
  35.  
  36. #ifndef CONFIG_FILE
  37. #define CONFIG_FILE "/sprite/lib/man/config"
  38. #endif
  39.  
  40. /*
  41.  * Information related to command-line options:
  42.  */
  43.  
  44. int typeset = 0;        /* Non-zero means print on typesetter
  45.                  * instead of on terminal. */
  46. int noMore = 0;            /* Non-zero means don't filter output through
  47.                  * the "more" program. */
  48. char *sectionName = NULL;    /* Name of section to search in. */
  49. char *configFile = CONFIG_FILE;    /* Configuration file that describes where
  50.                  * man pages are located. */
  51. int reformat = 0;        /* Non-zero means reformat man page even if
  52.                  * formatted copy appears to be up-to-date. */
  53. int makeIndex = 0;        /* Non-zero means generate index from args
  54.                  * rather than printing man pages. */
  55. int keywordLookup = 0;        /* Non-zero means "man -k": look for
  56.                  * keywords. */
  57. int where = 0;            /* Non-zero means say where man page is. */
  58. int doAll = 0;            /* Non-zero means check all directories. */
  59.  
  60. Option optionArray[] = {
  61.     {OPT_TRUE, "a", (char *) &doAll,
  62.         "Check all man directories (slower)"},
  63.     {OPT_STRING, "c", (char *) &configFile,
  64.         "Name of configuration file (default: /sprite/lib/man/config)"},
  65.     {OPT_TRUE, "f", (char *) &keywordLookup,
  66.         "Identical to \"-k\" (provided for UNIX compatibility)"},
  67.     {OPT_TRUE, "i", (char *) &makeIndex,
  68.         "Generate index from file name arguments"},
  69.     {OPT_TRUE, "k", (char *) &keywordLookup,
  70.         "Print index information for keyword arguments"},
  71.     {OPT_TRUE, "r", (char *) &reformat,
  72.         "Force man page to be reformatted, even if up-to-date"},
  73.     {OPT_STRING, "s", (char *) §ionName,
  74.         "Section name in which to search for man page(s)"},
  75.     {OPT_TRUE, "t", (char *) &typeset,
  76.         "Print man page on typesetter instead of on terminal"},
  77.     {OPT_TRUE, "w", (char *) &where,
  78.         "Print where the man page was found"},
  79.     {OPT_TRUE, "", (char *) &noMore,
  80.         "Don't filter output through \"more\" program"},
  81. };
  82.  
  83. /*
  84.  * One of the data structures built up by this program is the one
  85.  * that describes the directories containing man page sources, and
  86.  * the corresponding directories containing pre-formatted man pages.
  87.  */
  88.  
  89. typedef struct {
  90.     char *sourceDir;        /* Directory holding man page sources. */
  91.     char *fmtDir;        /* Directory holding formatted entries,
  92.                  * by same name. */
  93.     char *sectionName;        /* Preferred name for this section of the
  94.                  * manual. */
  95.     int allflag;        /* 1 if we check for everything, not just
  96.                  * foo.man */
  97. } ManDir;
  98.  
  99. #define MAX_DIRS 100
  100. ManDir dirs[MAX_DIRS];
  101. int numDirs;            /* Number of valid entries in dirs. */
  102.  
  103. /*
  104.  * The data structure below is used to hold all the index information
  105.  * associated with a manual entry.
  106.  */
  107.  
  108. #define MAX_NAMES 100
  109. #define MAX_KEYWORDS 100
  110. #define NAME_CHARS 1000
  111. #define KEYWORD_CHARS 1000
  112. #define SYNOPSIS_CHARS 100
  113. #define FILE_CHARS 100
  114.  
  115. typedef struct {
  116.     char fileName[FILE_CHARS];        /* Base name of file containing man
  117.                      * page (everything up to "."). */
  118.     char *names[MAX_NAMES+1];        /* Names of procedures or programs
  119.                      * described by this entry.  These
  120.                      * fields come from the "NAME" manual
  121.                      * section.  Terminated by a NULL
  122.                      * pointer. */
  123.     char synopsis[SYNOPSIS_CHARS];    /* Short description of the entry.
  124.                      * Comes from the part of the "NAME"
  125.                      * section that follows the dash. */
  126.     char *keywords[MAX_KEYWORDS+1];    /* Keywords associated with this
  127.                      * manual entry.  Comes from the
  128.                      * "KEYWORD" section of the entry, if
  129.                      * there is one.  Terminated by a
  130.                      * NULL pointer. */
  131.     char nameBuffer[NAME_CHARS];    /* Storage space for names. */
  132.     char keywordBuffer[KEYWORD_CHARS];    /* Storage space for keywords. */
  133. } IndexEntry;
  134.  
  135. char * strcasestr();
  136.  
  137. /*
  138.  * Commands to use for formatting and printing manual pages.  The %s's
  139.  * in these commands get filled in with particular file names in the
  140.  * code below.
  141.  */
  142.  
  143. #define FORMAT            "nroff -man -Tcrt %s > %s"
  144. #define FORMAT_PRINT        "nroff -man -Tcrt %s | %s -s"
  145. #define FORMAT_PRINT_NO_MORE    "nroff -man -Tcrt %s"
  146. #define PRINT            "%s -s %s"
  147. #define PRINT_NO_MORE        "cat %s"
  148. #define TYPESET            "ditroff -man %s"
  149.  
  150. /*
  151.  *----------------------------------------------------------------------
  152.  *
  153.  * NextLine --
  154.  *
  155.  *    Read the next line of a given file, skipping comment lines.
  156.  *    Break the line up into fields separated by white space.
  157.  *
  158.  * Results:
  159.  *    The return value is the number of fields in the line (i.e. the
  160.  *    number of elements of argv that are now valid).  If EOF was
  161.  *    encountered, then the return value is -1.  The fields pointed
  162.  *    to by argv are allocated in static storage, so they'll only
  163.  *    be valid up until the next call to this procedure.
  164.  *
  165.  * Side effects:
  166.  *    The argv array is modified.  If the line contains more fields than
  167.  *    are permitted by maxArgs, then an error message is output on stderr
  168.  *    and the extra fields are ignored.
  169.  *
  170.  *----------------------------------------------------------------------
  171.  */
  172.  
  173. int
  174. NextLine(file, maxArgs, argv)
  175.     FILE *file;            /* File from which to read. */
  176.     int maxArgs;        /* Number of entries in argv. */
  177.     char **argv;        /* Array to fill in with pointers to the
  178.                  * fields of the line. */
  179. {
  180. #define MAX_CHARS 200
  181.     static char buffer[MAX_CHARS];
  182.     register char *p;
  183.     int i;
  184.  
  185.     while (1) {
  186.     if (fgets(buffer, MAX_CHARS, file) == NULL) {
  187.         return -1;
  188.     }
  189.     for (p = buffer; ; p++) {
  190.         if (isspace(*p)) {
  191.         continue;
  192.         }
  193.         if ((*p != '#') && (*p != 0)) {
  194.         goto gotLine;
  195.         }
  196.         break;
  197.     }
  198.     }
  199.  
  200.     /*
  201.      * Break the line up into fields.
  202.      */
  203.  
  204.     gotLine:
  205.     for (i = 0, p = buffer; ; i++) {
  206.     while (isspace(*p)) {
  207.         p++;
  208.     }
  209.     if (*p == 0) {
  210.         return i;
  211.     }
  212.     if (i >= maxArgs) {
  213.         break;
  214.     }
  215.     argv[i] = p;
  216.     while (!isspace(*p)) {
  217.         p++;
  218.     }
  219.     *p = 0;
  220.     p++;
  221.     }
  222.  
  223.     /*
  224.      * Ran out of space to store field info.
  225.      */
  226.  
  227.     fprintf(stderr,
  228.         "More than %d fields in config file line;  extras ignored.\n",
  229.         maxArgs);
  230.     return maxArgs;
  231. }
  232.  
  233. /*
  234.  *----------------------------------------------------------------------
  235.  *
  236.  * ReadConfig --
  237.  *
  238.  *    This procedure reads in the configuration file given by
  239.  *    name, and builds a list of all the directories that match
  240.  *    the given section.
  241.  *
  242.  * Results:
  243.  *    Zero is returned if all went well, -1 if there was an error.
  244.  *
  245.  * Side effects:
  246.  *    The dirs data structure is created.  If an error occurred, then
  247.  *    an error message is printed.
  248.  *
  249.  *----------------------------------------------------------------------
  250.  */
  251.  
  252. int
  253. ReadConfig(name, section)
  254.     char *name;            /* Name of config file. */
  255.     char *section;        /* Only consider directories that match
  256.                  * this string;  if NULL, consider
  257.                  * everything. */
  258. {
  259. #define MAX_FIELDS 50
  260.     char *argv[MAX_FIELDS];
  261.     FILE *f;
  262.     int j, argc;
  263.     int allflag;
  264.  
  265.     f = fopen(name, "r");
  266.     if (f == NULL) {
  267.     fprintf(stderr, "Couldn't open \"%s\": %s.\n", name, strerror(errno));
  268.     return -1;
  269.     }
  270.  
  271.     for (numDirs = 0; numDirs < MAX_DIRS; ) {
  272.     allflag=0;
  273.     argc = NextLine(f, MAX_FIELDS, argv);
  274.     if (argc < 0) {
  275.         fclose(f);
  276.         return 0;
  277.     }
  278.     for (j = 2; j < argc; j++) {
  279.         if ((section != NULL) && (strcmp(argv[j], section) == 0)) {
  280.         goto makeEntry;
  281.         }
  282.         if (strcmp(argv[j], "ALL")==0) {
  283.         allflag = 1;
  284.         }
  285.     }
  286.     if (section != NULL) {
  287.         continue;
  288.     }
  289.     if (allflag==1 && doAll==0) {
  290.         continue;
  291.     }
  292.  
  293.     /*
  294.      * This line matched the section name;  add an entry to dirs.
  295.      */
  296.  
  297.     makeEntry:
  298.     dirs[numDirs].sourceDir =
  299.         (char *) malloc((unsigned) (strlen(argv[0]) + 1));
  300.     strcpy(dirs[numDirs].sourceDir, argv[0]);
  301.     dirs[numDirs].fmtDir =
  302.         (char *) malloc((unsigned) (strlen(argv[1]) + 1));
  303.     strcpy(dirs[numDirs].fmtDir, argv[1]);
  304.     dirs[numDirs].sectionName =
  305.         (char *) malloc((unsigned) (strlen(argv[2]) + 1));
  306.     strcpy(dirs[numDirs].sectionName, argv[2]);
  307.     dirs[numDirs].allflag = allflag;
  308.     numDirs++;
  309.     }
  310.  
  311.     fprintf(stderr, "Too many lines in \"%s\": ignoring extras.\n", name);
  312.     return 0;
  313. }
  314.  
  315. /*
  316.  *----------------------------------------------------------------------
  317.  *
  318.  * FindSection --
  319.  *
  320.  *    Advance a file just past the header line for a given section.
  321.  *
  322.  * Results:
  323.  *    Returns 0 if the given section was found, -1 if EOF was reached
  324.  *    before the desired section.
  325.  *
  326.  * Side effects:
  327.  *    Characters are read from file until a line is found in the form
  328.  *    ".SH section".  The line and its terminating newline are read and
  329.  *    discarded.
  330.  *
  331.  *----------------------------------------------------------------------
  332.  */
  333.  
  334. int
  335. FindSection(file, section)
  336.     FILE *file;            /* File to read. */
  337.     char *section;        /* Section to search for. */
  338. {
  339. #define LINE_LENGTH 200
  340.     char line[LINE_LENGTH];
  341.     register char *p1, *p2;
  342.  
  343.     while (fgets(line, LINE_LENGTH, file) != NULL) {
  344.     if ((line[0] != '.') || (line[1] != 'S') || (line[2] != 'H')) {
  345.         continue;
  346.     }
  347.     for (p1 = &line[3]; isspace(*p1); p1++) {
  348.         /* Skip white space. */
  349.     }
  350.     for (p2 = section; ; p1++, p2++) {
  351.         if ((*p2 == 0) && ((*p1 == 0) || (isspace(*p1)))) {
  352.         return 0;
  353.         }
  354.         if (*p2 != *p1) {
  355.         break;
  356.         }
  357.     }
  358.     }
  359.     return -1;
  360. }
  361.  
  362. /*
  363.  *----------------------------------------------------------------------
  364.  *
  365.  * IndexFromMan --
  366.  *
  367.  *    Read the source file for a manual entry and generate an
  368.  *    index entry for it.
  369.  *
  370.  * Results:
  371.  *    Normally zero is returned, but if an error occurs then a
  372.  *    no-zero value is returned.  *indexPtr is filled in with
  373.  *    information describing this man page.
  374.  *
  375.  * Side effects:
  376.  *    If an error occurs in reading the entry, then an error
  377.  *    message gets printed.
  378.  *
  379.  *----------------------------------------------------------------------
  380.  */
  381.  
  382. int
  383. IndexFromMan(fileName, indexPtr)
  384.     char *fileName;            /* Name of file containing man page. */
  385.     register IndexEntry *indexPtr;    /* Pointer to index entry. */
  386. {
  387.     register FILE *file;
  388.     register char *p;
  389.     register int c;
  390.     char *limit;
  391.     int index;
  392.  
  393.     file = fopen(fileName, "r");
  394.     if (file == NULL) {
  395.     fprintf(stderr, "Couldn't open \"%s\": %s.\n", fileName,
  396.         strerror(errno));
  397.     return -1;
  398.     }
  399.  
  400.     /*
  401.      * Parse off the root of the file name.
  402.      */
  403.  
  404.     for (p = fileName; (*p != '.') && (*p != 0); p++) {
  405.     /* Null loop body. */
  406.     }
  407.     if ((p-fileName) > FILE_CHARS) {
  408.     fprintf(stderr, "File name \"%s\" too long.\n", fileName);
  409.     goto error;
  410.     }
  411.     strncpy(indexPtr->fileName, fileName, (p-fileName));
  412.     indexPtr->fileName[p-fileName] = 0;
  413.  
  414.     /*
  415.      * Parse off the names (a bunch of strings, all but the last of which
  416.      * are terminated by commas, with the last terminated by space).
  417.      */
  418.  
  419.     if (FindSection(file, "NAME") != 0) {
  420.     fprintf(stderr, "Couldn't find \"NAME\" section in \"%s\".\n",
  421.         fileName);
  422.     goto error;
  423.     }
  424.     c = getc(file);
  425.  
  426.     /*
  427.      * Skip any troff commands at the beginning of the section.
  428.      */
  429.  
  430.     while (c == '.') {
  431.     while ((c != '\n') && (c != EOF)) {
  432.         c = getc(file);
  433.     }
  434.     c = getc(file);
  435.     }
  436.  
  437.     /*
  438.      * Parse off the procedure names.
  439.      */
  440.  
  441.     p = indexPtr->nameBuffer;
  442.     limit = &indexPtr->nameBuffer[NAME_CHARS-1];
  443.     for (index = 0; index < MAX_NAMES; ) {
  444.     while (isspace(c)) {
  445.         c = getc(file);
  446.     }
  447.     indexPtr->names[index] = p;
  448.     while (!isspace(c) && (c != ',') && (p < limit)) {
  449.         *p = c;
  450.         p++;
  451.         c = getc(file);
  452.     }
  453.     if (p >= limit) {
  454.         break;
  455.     }
  456.     if (indexPtr->names[index] != p) {
  457.         *p = 0;
  458.         p++;
  459.         index++;
  460.     }
  461.     if (c != ',') {
  462.         break;
  463.     }
  464.     c = getc(file);
  465.     }
  466.     if (c == EOF) {
  467.     fprintf(stderr, "Unexpected end-of-file in NAME section of \"%s\".\n",
  468.         fileName);
  469.     goto error;
  470.     }
  471.     if ((index == MAX_NAMES) || (p >= limit)) {
  472.     fprintf(stderr, "Too many names in \"%s\";  skipped the extras.\n",
  473.         fileName);
  474.     }
  475.     indexPtr->names[index] = 0;
  476.  
  477.     /*
  478.      * Skip up to and through a hyphen and any following space, then
  479.      * use the rest of the line as a synopsis.
  480.      */
  481.  
  482.     while ((c != '-') && (c != EOF)) {
  483.     c = getc(file);
  484.     }
  485.     for (c = getc(file); isspace(c); c = getc(file)) {
  486.     /* Null loop body. */
  487.     }
  488.     ungetc(c, file);
  489.     fgets(indexPtr->synopsis, SYNOPSIS_CHARS, file);
  490.     for (p = indexPtr->synopsis; *p != 0; p++) {
  491.     if (*p == '\n') {
  492.         *p = 0;
  493.         break;
  494.     }
  495.     }
  496.  
  497.     /*
  498.      * Skip to the keywords section and parse off the keywords in a
  499.      * fashion similar to the names, except that (a) it's OK not to have
  500.      * a KEYWORDS section, (b) it's OK to have space in a keyword, and
  501.      * (c) the last keyword is terminated by newline.
  502.      */
  503.  
  504.     if (FindSection(file, "KEYWORDS") != 0) {
  505.     indexPtr->keywords[0] = NULL;
  506.     goto done;
  507.     }
  508.     c = getc(file);
  509.     p = indexPtr->keywordBuffer;
  510.     limit = &indexPtr->keywordBuffer[KEYWORD_CHARS-1];
  511.     for (index = 0; index < MAX_KEYWORDS; ) {
  512.     while (isspace(c)) {
  513.         c = getc(file);
  514.     }
  515.     indexPtr->keywords[index] = p;
  516.     while ((c != ',') && (c != '\n') && (c != EOF) && (p < limit)) {
  517.         *p = c;
  518.         p++;
  519.         c = getc(file);
  520.     }
  521.     if (p >= limit) {
  522.         break;
  523.     }
  524.     if (indexPtr->keywords[index] != p) {
  525.         *p = 0;
  526.         p++;
  527.         index++;
  528.     }
  529.     if (c == EOF) {
  530.         fprintf(stderr,
  531.             "Unexpected end-of-file in KEYWORDS section of \"%s\".\n",
  532.             fileName);
  533.         goto error;
  534.     }
  535.     if (c == '\n') {
  536.         break;
  537.     }
  538.     c = getc(file);
  539.     }
  540.     if ((index == MAX_NAMES) || (p >= limit)) {
  541.     fprintf(stderr, "Too many names in \"%s\";  skipped the extras.\n",
  542.         fileName);
  543.     }
  544.     indexPtr->keywords[index] = 0;
  545.  
  546.     done:
  547.     fclose(file);
  548.     return 0;
  549.  
  550.     error:
  551.     fclose(file);
  552.     return -1;
  553. }
  554.  
  555. /*
  556.  *----------------------------------------------------------------------
  557.  *
  558.  * PrintIndex --
  559.  *
  560.  *    Read manual pages and print index entries on standard output.
  561.  *
  562.  * Results:
  563.  *    None.
  564.  *
  565.  * Side effects:
  566.  *    Information gets printed on standard output.  Error messages
  567.  *    may appear on stderr.
  568.  *
  569.  *----------------------------------------------------------------------
  570.  */
  571.  
  572. void
  573. PrintIndex(argc, argv)
  574.     int argc;            /* Number of files to read. */
  575.     char **argv;        /* Names of files to read. */
  576. {
  577.     IndexEntry index;
  578.     int i;
  579.     char **p;
  580.  
  581.     for (i = 0; i < argc; i++) {
  582.     if (IndexFromMan(argv[i], &index) != 0) {
  583.         continue;
  584.     }
  585.     printf("%s\n", index.fileName);
  586.     for (p = index.names; *p != 0; p++) {
  587.         printf("%s, ", *p);
  588.     }
  589.     putchar('\n');
  590.     printf("%s\n", index.synopsis);
  591.     for (p = index.keywords; *p != 0; p++) {
  592.         printf("%s, ", *p);
  593.     }
  594.     putchar('\n');
  595.     }
  596. }
  597.  
  598. /*
  599.  *----------------------------------------------------------------------
  600.  *
  601.  * ReadNextIndex --
  602.  *
  603.  *    Given a handle for an index file created by PrintIndex, read
  604.  *    the next index entry from the file.
  605.  *
  606.  * Results:
  607.  *    Zero is returned if all went well;  otherwise -1 is returned
  608.  *    and an error message is printed on stderr.  The fields in
  609.  *    *indexPtr will be filled in with information describing this
  610.  *    index entry.
  611.  *
  612.  * Side effects:
  613.  *    The position in file is advanced.
  614.  *
  615.  *----------------------------------------------------------------------
  616.  */
  617.  
  618. int
  619. ReadNextIndex(file, indexPtr)
  620.     FILE *file;            /* File to read. */
  621.     IndexEntry *indexPtr;    /* Entry to fill in. */
  622. {
  623.     register char *p;
  624.     int i;
  625.  
  626.     /*
  627.      * Read in the file name line.
  628.      */
  629.  
  630.     if (fgets(indexPtr->fileName, FILE_CHARS, file) == NULL) {
  631.     return -1;
  632.     }
  633.     for (p = indexPtr->fileName; *p != '\n'; p++) {
  634.     if (*p == 0) {
  635.         fprintf(stderr, "Filename line for \"%s\" too long.\n",
  636.             indexPtr->fileName);
  637.         return -1;
  638.     }
  639.     }
  640.     *p = 0;
  641.  
  642.     /*
  643.      * Read in and parse the keyword line.
  644.      */
  645.  
  646.     if (fgets(indexPtr->nameBuffer, NAME_CHARS, file) == NULL) {
  647.     fprintf(stderr, "End-of-file in name line for \"%s\".\n",
  648.         indexPtr->fileName);
  649.     return -1;
  650.     }
  651.     for (i = 0, p = indexPtr->nameBuffer; i < MAX_NAMES; ) {
  652.     while (isspace(*p)) {
  653.         p++;
  654.     }
  655.     indexPtr->names[i] = p;
  656.     while ((*p != ',') && (*p != 0)) {
  657.         p++;
  658.     }
  659.     if (p != indexPtr->names[i]) {
  660.         i++;
  661.     }
  662.     if (*p == 0) {
  663.         break;
  664.     }
  665.     *p = 0;
  666.     p++;
  667.     }
  668.     indexPtr->names[i] = 0;
  669.     if ((p[-1] != '\n') || (i == MAX_NAMES)) {
  670.     fprintf(stderr, "Too many names for \"%s\".\n", indexPtr->fileName);
  671.     return -1;
  672.     }
  673.  
  674.     /*
  675.      * Read in the synopsis line;  there's no parsing to do.
  676.      */
  677.  
  678.     if (fgets(indexPtr->synopsis, SYNOPSIS_CHARS, file) == NULL) {
  679.     fprintf(stderr, "End-of-file in synopsis line for \"%s\".\n",
  680.         indexPtr->fileName);
  681.     return -1;
  682.     }
  683.     for (p = indexPtr->synopsis; *p != '\n'; p++) {
  684.     if (*p == 0) {
  685.         fprintf(stderr, "Synopsis line for \"%s\" too long.\n",
  686.             indexPtr->fileName);
  687.         return -1;
  688.     }
  689.     }
  690.     *p = 0;
  691.  
  692.     /*
  693.      * Read in and parse the keywords line.
  694.      */
  695.  
  696.     if (fgets(indexPtr->keywordBuffer, KEYWORD_CHARS, file) == NULL) {
  697.     fprintf(stderr, "End-of-file in keywords line for \"%s\".\n",
  698.         indexPtr->fileName);
  699.     return -1;
  700.     }
  701.     for (i = 0, p = indexPtr->keywordBuffer; i < MAX_KEYWORDS;) {
  702.     while (isspace(*p)) {
  703.         p++;
  704.     }
  705.     indexPtr->keywords[i] = p;
  706.     while ((*p != ',') && (*p != 0)) {
  707.         p++;
  708.     }
  709.     if (p != indexPtr->keywords[i]) {
  710.         i++;
  711.     }
  712.     if (*p == 0) {
  713.         break;
  714.     }
  715.     *p = 0;
  716.     p++;
  717.     }
  718.     indexPtr->keywords[i] = 0;
  719.     if ((p[-1] != '\n') || (i == MAX_KEYWORDS)) {
  720.     fprintf(stderr, "Too many keywords for \"%s\".\n", indexPtr->fileName);
  721.     return -1;
  722.     }
  723.     return 0;
  724. }
  725.  
  726. /*
  727.  *----------------------------------------------------------------------
  728.  *
  729.  * PrettyPrintIndex --
  730.  *
  731.  *    Print an index entry in human-readable form on standard output.
  732.  *
  733.  * Results:
  734.  *    None.
  735.  *
  736.  * Side effects:
  737.  *    None.
  738.  *
  739.  *----------------------------------------------------------------------
  740.  */
  741.  
  742. void
  743. PrettyPrintIndex(indexPtr, section)
  744.     IndexEntry *indexPtr;    /* Entry to print. */
  745.     char *section;        /* Name to use for manual section. */
  746. {
  747. #define COLS_FOR_NAMES 24
  748.     int i, numCols;
  749.  
  750.     numCols = 0;
  751.     for (i = 0; indexPtr->names[i] != 0; i++) {
  752.     if (i != 0) {
  753.         fputs(", ", stdout);
  754.         numCols += 2;
  755.     }
  756.     fputs(indexPtr->names[i], stdout);
  757.     numCols += strlen(indexPtr->names[i]);
  758.     }
  759.     numCols += printf(" (%s) ", section);
  760.     if (numCols < COLS_FOR_NAMES) {
  761.     printf("%*c", COLS_FOR_NAMES-numCols, ' ');
  762.     }
  763.     printf("- %s\n", indexPtr->synopsis);
  764. }
  765.  
  766. /*
  767.  *----------------------------------------------------------------------
  768.  *
  769.  * PrintByKeyword --
  770.  *
  771.  *    Given a keyword, locate the index entries (if any) corresponding
  772.  *    to that keyword and print out each matching index entry.
  773.  *
  774.  * Results:
  775.  *    Information is printed for each index entry that contains
  776.  *    the keyword as a substring of the entry's name, synopsis, or
  777.  *    keyword fields.  If no matching entry was found, then an
  778.  *    error message is printed.
  779.  *
  780.  * Side effects:
  781.  *    None.
  782.  *
  783.  *----------------------------------------------------------------------
  784.  */
  785.  
  786. int
  787. PrintByKeyword(keyword)
  788.     char *keyword;            /* String to search for. */
  789. {
  790.     int i, foundMatch;
  791.     FILE *f;
  792.     char **p;
  793.     char indexName[400];
  794.     IndexEntry index;
  795.  
  796.     foundMatch = 0;
  797.     for (i = 0; i < numDirs; i++) {
  798.     sprintf(indexName, "%.350s/index", dirs[i].sourceDir);
  799.     f = fopen(indexName, "r");
  800.     if (f == NULL) {
  801.         continue;
  802.     }
  803.     while (ReadNextIndex(f, &index) == 0) {
  804.         for (p = index.names; *p != 0; p++) {
  805.         if (strcasestr(*p, keyword) != 0) {
  806.             goto found;
  807.         }
  808.         }
  809.         if (strcasestr(index.synopsis, keyword) != 0) {
  810.         goto found;
  811.         }
  812.         for (p = index.keywords; *p != 0; p++) {
  813.         if (strcasestr(*p, keyword) != 0) {
  814.             goto found;
  815.         }
  816.         }
  817.         continue;
  818.  
  819.         found:
  820.         PrettyPrintIndex(&index, dirs[i].sectionName);
  821.         foundMatch = 1;
  822.     }
  823.     fclose(f);
  824.     }
  825.     if (!foundMatch) {
  826.     fprintf(stderr,
  827.         "Couldn't find any manual entries related to \"%s\".\n",
  828.         keyword);
  829.     }
  830. }
  831.  
  832. #define BUFLEN 10
  833. char suffixBuf[BUFLEN];
  834. /*
  835.  *----------------------------------------------------------------------
  836.  *
  837.  * search --
  838.  *
  839.  *    Search for a file dir/name.suffix, where suffix is unknown.
  840.  *
  841.  * Results:
  842.  *    0 for success.
  843.  *    Returns suffix.
  844.  *
  845.  * Side effects:
  846.  *    None.
  847.  *
  848.  *----------------------------------------------------------------------
  849.  */
  850. search(dir, name, suffix)
  851.     char *dir;
  852.     char *name;
  853.     char **suffix;
  854. {
  855.     DIR *dirp;
  856.     struct direct *direct;
  857.     int len = strlen(name);
  858.     int status = 1;
  859.  
  860.     dirp = opendir(dir);
  861.     if (dir==NULL) {
  862.     fprintf(stderr,"man: couldn't open %s\n", dir);
  863.     return 1;
  864.     }
  865.  
  866.     while (1) {
  867.     direct = readdir(dirp);
  868.     if (direct==NULL) {
  869.         break;
  870.     }
  871.     if (strncmp(name,direct->d_name,len)==0 &&
  872.         direct->d_name[len]=='.') {
  873.         strncpy(suffixBuf, direct->d_name+len+1, BUFLEN);
  874.         suffixBuf[BUFLEN]='\0';
  875.         *suffix = suffixBuf;
  876.         status = 0;
  877.         break;
  878.     }
  879.  
  880.     }
  881.  
  882.     closedir(dirp);
  883.     return status;
  884.     
  885. }
  886.  
  887. /*
  888.  *----------------------------------------------------------------------
  889.  *
  890.  * main --
  891.  *
  892.  *    This is the main program, which runs the whole show.
  893.  *
  894.  * Results:
  895.  *    None.
  896.  *
  897.  * Side effects:
  898.  *    Read the man page for details.
  899.  *
  900.  *----------------------------------------------------------------------
  901.  */
  902.  
  903. main(argc, argv)
  904.     int argc;
  905.     char **argv;
  906. {
  907.     int i, result;
  908.     char srcName[500], fmtName[500];
  909.     char command[1100];
  910.     char *progName = argv[0];
  911.     char *pager;
  912.     struct stat srcStat, fmtStat;
  913.     IndexEntry index;
  914.     char *suffix;
  915.  
  916.     pager = getenv("PAGER");
  917.     if(pager == 0) pager = "more";
  918.  
  919.     /*
  920.      * Process command-line options and figure out what section to
  921.      * look in.
  922.      */
  923.  
  924.     result = 0;
  925.     argc = Opt_Parse(argc, argv, optionArray, Opt_Number(optionArray), 0);
  926.     if ((sectionName == NULL) && (argc > 1) && (isdigit(argv[1][0]))) {
  927.     sectionName = argv[1];
  928.     argc--;
  929.     argv++;
  930.     }
  931.  
  932.     /*
  933.      * If we're just generating an index, do it here and quit.
  934.      */
  935.  
  936.     if (makeIndex) {
  937.     PrintIndex(argc-1, &argv[1]);
  938.     exit(0);
  939.     }
  940.  
  941.     /*
  942.      * Read in the configuration file, and make sure that there is at
  943.      * least one place to look for the desired manual page.
  944.      */
  945.  
  946.     if (ReadConfig(configFile, sectionName) != 0) {
  947.     exit(1);
  948.     }
  949.     if (numDirs == 0) {
  950.     if (sectionName != NULL) {
  951.         fprintf(stderr, "No manual section named \"%s\".\n", sectionName);
  952.     } else {
  953.         fprintf(stderr, "The config file (%s) is empty!\n", configFile);
  954.     }
  955.     exit(1);
  956.     }
  957.  
  958.     if (argc == 1) {
  959.     fprintf(stderr, "Usage:  %s [options] entryName entryName ...\n",
  960.         progName);
  961.     exit(1);
  962.     }
  963.  
  964.     /*
  965.      * Loop over all of the named man pages, processing each one separately.
  966.      */
  967.  
  968.     for (argv++ ; argc > 1; argc--, argv++) {
  969.     /*
  970.      * Handle special case of keyword lookup.
  971.      */
  972.  
  973.     if (keywordLookup) {
  974.         PrintByKeyword(argv[0]);
  975.         continue;
  976.     }
  977.  
  978.     /*
  979.      * Search for the desired entry in two passes.  In the first pass,
  980.      * look in each of the available directories for a file named
  981.      * "foo.man" where foo is the entry's name.  If this doesn't work,
  982.      * then the second pass reads the index files in each of the
  983.      * available directories, checking for an entry with the desired
  984.      * name.
  985.      * If allflag is set for the directory, we then do two more pases:
  986.      * In the third pass, we look for a file named "foo.section".
  987.      * Finally, we look for "foo.*"
  988.      */
  989.     
  990.     for (i = 0; i < numDirs; i++) {
  991.         sprintf(srcName, "%.350s/%.100s.man", dirs[i].sourceDir, argv[0]);
  992.         if (access(srcName, R_OK) == 0) {
  993.         sprintf(fmtName, "%.350s/%.100s.man", dirs[i].fmtDir, argv[0]);
  994.         goto gotEntry;
  995.         }
  996.     }
  997.     for (i = 0; i < numDirs; i++) {
  998.         FILE *f;
  999.         char **p;
  1000.  
  1001.         sprintf(srcName, "%.350s/index", dirs[i].sourceDir);
  1002.         f = fopen(srcName, "r");
  1003.         if (f == NULL) {
  1004.         continue;
  1005.         }
  1006.         while (ReadNextIndex(f, &index) == 0) {
  1007.         for (p = index.names; *p != 0; p++) {
  1008.             if (strcmp(*p, argv[0]) == 0) {
  1009.             sprintf(srcName, "%.350s/%.100s.man",
  1010.                 dirs[i].sourceDir, index.fileName);
  1011.             if (access(srcName, R_OK) == 0) {
  1012.                 sprintf(fmtName, "%.350s/%.100s.man",
  1013.                     dirs[i].fmtDir, index.fileName);
  1014.                 fclose(f);
  1015.                 goto gotEntry;
  1016.             }
  1017.             }
  1018.         }
  1019.         }
  1020.         fclose(f);
  1021.     }
  1022.  
  1023.     if (strchr(argv[0], '.') != NULL) {
  1024.         sprintf(srcName, "./%.100s", argv[0]);
  1025.         if (access(srcName, R_OK) == 0) {
  1026.         sprintf(fmtName, "/tmp/%.100s", argv[0]);
  1027.         i = 0;
  1028.         goto gotEntry;
  1029.         }
  1030.     }
  1031.  
  1032.     for (i = 0; i < numDirs; i++) {
  1033.         if (dirs[i].allflag && dirs[i].sectionName) {
  1034.         sprintf(srcName, "%.350s/%.100s.%s", dirs[i].sourceDir,
  1035.             argv[0], dirs[i].sectionName);
  1036.         if (access(srcName, R_OK) == 0) {
  1037.             sprintf(fmtName, "%.350s/%.100s.%s", dirs[i].fmtDir,
  1038.                 argv[0], dirs[i].sectionName);
  1039.             goto gotEntry;
  1040.         }
  1041.         }
  1042.     }
  1043.     for (i = 0; i < numDirs; i++) {
  1044.         if (dirs[i].allflag &&
  1045.             search(dirs[i].sourceDir, argv[0],&suffix)==0) {
  1046.         sprintf(srcName, "%.350s/%.100s.%s", dirs[i].sourceDir, argv[0],
  1047.             suffix);
  1048.         sprintf(fmtName, "%.350s/%.100s.%s", dirs[i].fmtDir, argv[0],
  1049.                 suffix);
  1050.         goto gotEntry;
  1051.         }
  1052.     }
  1053.  
  1054.     /*
  1055.      * Couldn't find the manual entry.
  1056.      */
  1057.  
  1058.     if (sectionName == NULL) {
  1059.         fprintf(stderr, "No manual entry for \"%s\".\n", argv[0]);
  1060.     } else {
  1061.         fprintf(stderr,
  1062.             "No manual entry for \"%s\" in section \"%s\".\n",
  1063.             argv[0], sectionName);
  1064.     }
  1065.     result = 1;
  1066.     continue;
  1067.  
  1068.     /*
  1069.      * If the entry is to be typeset, just do it.
  1070.      */
  1071.  
  1072.     gotEntry:
  1073.  
  1074.     if (where) {
  1075.         printf("Man page found in %s\n", fmtName);
  1076.         continue;
  1077.     }
  1078.  
  1079.     if (typeset) {
  1080.         sprintf(command, TYPESET, srcName);
  1081.         if (system(command) != 0) {
  1082.         printf("Error in typesetting \"%s\" man page.\n", argv[0]);
  1083.         result = 1;
  1084.         }
  1085.         continue;
  1086.     }
  1087.  
  1088.     /*
  1089.      * See if there is an up-to-date formatted version of the page.
  1090.      * If not, then regenerate it.  (If the formatted directory is
  1091.      * "-" that means no formatted version of the man page is to be
  1092.      * kept)
  1093.      */
  1094.  
  1095.     if (strcmp(dirs[i].fmtDir, "-") == 0) {
  1096.         goto couldntFormat;
  1097.     }
  1098.  
  1099.     if ((stat(srcName, &srcStat) != 0) || (stat(fmtName, &fmtStat) != 0)
  1100.         || (srcStat.st_mtime > fmtStat.st_mtime) || reformat) {
  1101.         printf("Reformatting manual entry.  Please wait...\n");
  1102.         sprintf(command, FORMAT, srcName, fmtName);
  1103.         if (system(command) != 0) {
  1104.         unlink(fmtName);
  1105.         goto couldntFormat;
  1106.         }
  1107.  
  1108.         /*
  1109.          * Reprotect the formatted file so that anyone can overwrite
  1110.          * it later.
  1111.          */
  1112.  
  1113.         chmod(fmtName, 0666);
  1114.     }
  1115.  
  1116.     /*
  1117.      * Print the formatted version of the man page.
  1118.      */
  1119.  
  1120.     if (noMore) {
  1121.         sprintf(command, PRINT_NO_MORE, fmtName);
  1122.     } else {
  1123.         sprintf(command, PRINT, pager, fmtName);
  1124.     }
  1125.     if (system(command) == 0) {
  1126.         continue;
  1127.     }
  1128.  
  1129.     /*
  1130.      * We get here if it wasn't possible to format and/or print the
  1131.      * man page.  Try one last desperation move:  print and format
  1132.      * in a single command.
  1133.      */
  1134.  
  1135.     couldntFormat:
  1136.     if (noMore) {
  1137.         sprintf(command, FORMAT_PRINT_NO_MORE, srcName);
  1138.     } else {
  1139.         sprintf(command, FORMAT_PRINT, srcName, pager);
  1140.     }
  1141.     (void) system(command);
  1142.     }
  1143.     exit(result);
  1144. }
  1145.  
  1146.  
  1147. /*
  1148.  *----------------------------------------------------------------------
  1149.  *
  1150.  * strcasestr --
  1151.  *
  1152.  *    Locate the first instance of a substring in a string, ignoring
  1153.  *    case.  This is the code for strstr, very slightly modified.
  1154.  *
  1155.  * Results:
  1156.  *    If string contains substring, the return value is the
  1157.  *    location of the first matching instance of substring
  1158.  *    in string.  If string doesn't contain substring, the
  1159.  *    return value is 0.  Matching is done on an exact
  1160.  *    character-for-character basis with no wildcards or special
  1161.  *    characters.
  1162.  *
  1163.  * Side effects:
  1164.  *    None.
  1165.  *
  1166.  *----------------------------------------------------------------------
  1167.  */
  1168.  
  1169. #define makeupper(x) (islower(x)?toupper(x):(x))
  1170.  
  1171. char *
  1172. strcasestr(string, substring)
  1173.     register char *string;    /* String to search. */
  1174.     char *substring;        /* Substring to try to find in string. */
  1175. {
  1176.     register char *a, *b;
  1177.  
  1178.     /* First scan quickly through the two strings looking for a
  1179.      * single-character match.  When it's found, then compare the
  1180.      * rest of the substring.
  1181.      */
  1182.  
  1183.     b = substring;
  1184.     if (*b == 0) {
  1185.     return string;
  1186.     }
  1187.     for ( ; *string != 0; string += 1) {
  1188.     if (makeupper(*string) != makeupper(*b)) {
  1189.         continue;
  1190.     }
  1191.     a = string;
  1192.     while (1) {
  1193.         if (*b == 0) {
  1194.         return string;
  1195.         }
  1196.         if (makeupper(*a) != makeupper(*b)) {
  1197.         a++;
  1198.         b++;
  1199.         break;
  1200.         }
  1201.         a++;
  1202.         b++;
  1203.     }
  1204.     b = substring;
  1205.     }
  1206.     return (char *) 0;
  1207. }
  1208.